package cn.song.logread.controller;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.song.logread.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 系统-web日志控制器
 * 功能：日志文件查询、资源路径检查、根路径访问控制
 *
 * @author SongRenShuo
 * @date 2025/03/19
 */
@Slf4j
@RestController
@RequestMapping("/sys/weblog")
public class WebLogController {

    /* ---------------------------------------- region 缓存配置 ---------------------------------------- */
    /**
     * 缓存总字节数
     */
    private static Long totalCacheBytes = 0L;
    /**
     * 日志缓存
     */
    private static final Map<String, Map<String, R<String>>> LOG_CACHE = new LinkedHashMap<>();
    /**
     * 路径请求计数器
     */
    private static Long pathRequestCount = 0L;
    /**
     * 路径缓存
     */
    private static final Map<String, Map<String, R<List<Map<String, String>>>>> PATH_CACHE = new LinkedHashMap<>();
    /**
     * 白名单缓存
     */
    private static List<JSONObject> whitelistListCache = new ArrayList<>();
    /**
     * 生产环境黑名单缓存
     */
    private static List<String> prodBlacklistListCache = new ArrayList<>();

    /* ---------------------------------------- endregion ---------------------------------------- */

    /* ---------------------------------------- region 配置参数 ---------------------------------------- */
    /**
     * 环境：dev/开发环境、prod/生产环境
     */
    @Value("${active}")
    private String active;
    /**
     * 生产环境黑名单 包含设定的关键字文件列表不展示
     */
    @Value("${prodBlacklistList}")
    private String prodBlacklistList;
    /**
     * 默认资源路径
     */
    @Value("${resourcePathDefault}")
    private String resourcePathDefault;
    /**
     * 白名单JSON字符串
     */
    @Value("${resourcePathWhitelistList}")
    private String resourcePathWhitelistStr;
    /**
     * 管理员密码
     */
    @Value("${adminPassWord}")
    private String adminPassWord;
    /**
     * 最大返回日志文件数
     */
    @Value("${maximumNumberLogFilesThatCanBeReturned:30}")
    private int maxLogFiles;
    /**
     * 最大缓存条目数
     */
    @Value("${maxCacheEntries:20}")
    private int maxCacheEntries;
    /**
     * 最大缓存大小
     */
    @Value("${maxCacheSize:20}")
    private long maxCacheSize;
    /**
     * 最大路径请求缓存次数
     */
    @Value("${maxPathRequests:5}")
    private int maxPathRequests;
    /**
     * 最大响应体大小 MB
     */
    @Value("${maxResponseBodySize:10}")
    private int maxResponseBodySize;
    /**
     * 最大响应时长 毫秒
     */
    @Value("${maxResponseTime:20000}")
    private long maxResponseTime;

    /* ---------------------------------------- endregion ---------------------------------------- */

    /**
     * 日志前缀正则表达式
     * <p>
     * 示例前缀：
     * <pre>
     * 2025-03-27 15:30:00  [ SimpleAsyncTaskExecutor-411:98999365 ]
     * 2025-03-27 16:53:14.585 [http-nio-8072-exec-4]
     */
    private static final Pattern LOG_PREFIX_PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?\\s+\\[.*?]");

    /**
     * 检查资源路径有效性
     *
     * @param resourcePath  待检查路径（未指定时使用默认路径）
     * @param queryFileName 查询文件名（用于过滤）
     */
    @GetMapping("/check-resource")
    public R<List<Map<String, String>>> checkResource(
            String resourcePath,
            @RequestParam(defaultValue = "") String queryFileName) {

        if (StrUtil.isBlank(resourcePathDefault)) {
            return R.failed("资源路径未配置，请联系管理员");
        }

        resourcePath = StrUtil.blankToDefault(resourcePath, resourcePathDefault);
        pathRequestCount++;

        // 尝试从缓存获取结果
        if (PATH_CACHE.containsKey(resourcePath) && PATH_CACHE.get(resourcePath).containsKey(queryFileName)) {
            log.info("路径缓存命中 [{}] 文件名[{}] 缓存量[{}] 请求次数[{}]",
                    resourcePath, queryFileName, PATH_CACHE.size(), pathRequestCount);
            R<List<Map<String, String>>> listR = PATH_CACHE.get(resourcePath).get(queryFileName);
            checkAndClearPathCache();
            return listR;
        }

        // 缓存未命中时查询文件系统
        R<List<Map<String, String>>> result = queryFileSystem(resourcePath, queryFileName);
        if (StrUtil.isBlank(queryFileName)) {
            PATH_CACHE.computeIfAbsent(resourcePath, k -> new LinkedHashMap<>())
                    .put(queryFileName, result);
        }
        return result;
    }

    /**
     * 查询日志内容
     *
     * @param lookFilePath                日志文件路径
     * @param logContent                  查询关键词
     * @param whetherToEnableRegex        是否启用正则表达式
     * @param whetherVerboseModeIsEnabled 是否启用详细模式(换行日志、堆栈日志)
     */
    @GetMapping("/queryLogs")
    public R<String> queryLogs(
            String lookFilePath, String logContent,
            @RequestParam(defaultValue = "false") Boolean whetherToEnableRegex,
            @RequestParam(defaultValue = "false") Boolean whetherVerboseModeIsEnabled) {

        // 尝试从缓存获取结果
        if (LOG_CACHE.containsKey(lookFilePath) && LOG_CACHE.get(lookFilePath).containsKey(logContent) && !whetherToEnableRegex) {
            logCacheStatus(lookFilePath, logContent);
            return LOG_CACHE.get(lookFilePath).get(logContent);
        }

        // 缓存未命中时读取文件
        R<String> result = readLogFile(lookFilePath, logContent, whetherToEnableRegex, whetherVerboseModeIsEnabled);
        if (!whetherToEnableRegex) {
            updateLogCache(lookFilePath, logContent, result);
        }
        return result;
    }

    /**
     * 查询根路径访问权限（需管理员密码）
     */
    @GetMapping("/queryRootPathAccessibleLogs")
    public R<List<Map<String, Object>>> queryRootPathAccessibleLogs(
            String passWord,
            String queryName) {

        if (!adminPassWord.equals(passWord)) {
            return R.failed("管理员密码错误");
        }

        // 初始化白名单缓存
        initializeWhitelistCache();

        // 过滤查询结果
        List<Map<String, Object>> result = whitelistListCache.stream()
                .filter(item -> StrUtil.isBlank(queryName) || item.getStr("description").contains(queryName))
                .collect(Collectors.toList());

        // 查找默认路径
        JSONObject defaultPath = whitelistListCache.stream()
                .filter(item -> item.getStr("resourcePath").equals(resourcePathDefault))
                .findFirst()
                .orElse(new JSONObject());

        return R.success(result).add("resourcePathDefault", defaultPath);
    }

    /* ---------------------------------------- region 私有方法 ---------------------------------------- */

    /**
     * 读取日志文件内容
     */
    private R<String> readLogFile(String path, String keyword, boolean useRegex, boolean whetherVerboseModeIsEnabled) {
        // 基础校验
        if (StrUtil.hasBlank(path, keyword)) {
            return R.failed("参数缺失");
        }
        if (!path.matches(".*\\.(txt|log)$")) {
            return R.failed("仅支持txt/log文件类型");
        }

        File file = new File(path);
        if (!file.exists() || !file.canRead()) {
            log.error("文件访问异常 [{}]", path);
            return R.failed("文件不存在或无权限");
        }

        // 新增：记录开始时间
        long startTime = System.currentTimeMillis();

        try (BufferedReader reader = new BufferedReader(new FileReader(file), 1024 * 1024)) {
            StringBuilder content = new StringBuilder();
            Pattern pattern = useRegex ? Pattern.compile(keyword) : null;
            String line;
            long contentSize = 0;
            // 新增预计算截止时间和最大size
            long timeoutDeadline = startTime + maxResponseTime;
            long maxSize = maxResponseBodySize * 1024 * 1024L;
            // 新增检查计数器
            int checkCount = 0;
            final int checkInterval = 10;

            while ((line = reader.readLine()) != null) {
                checkCount++;
                if (checkCount % checkInterval == 0) {
                    if (System.currentTimeMillis() > timeoutDeadline) {
                        return R.failed("查询超时，请缩短查询范围");
                    }
                }

                if (!matchLine(line, keyword, pattern)) {
                    continue;
                }

                content.append("\r\n").append(line);
                contentSize += line.getBytes().length;
                if (contentSize > maxSize) {
                    return R.failed("查询结果过大，请输入更长的关键字");
                }
                if (!whetherVerboseModeIsEnabled) {
                    content.append("\r\n\r\n");
                    continue;
                }


                while ((line = reader.readLine()) != null) {
                    if (System.currentTimeMillis() > timeoutDeadline) {
                        return R.failed("查询超时，请输入更长的关键字");
                    }
                    // 优化：缓存matcher结果减少对象创建
                    boolean isLogPrefix = LOG_PREFIX_PATTERN.matcher(line).find();
                    if (isLogPrefix) {
                        content.append("\r\n\r\n");
                        if (!matchLine(line, keyword, pattern)) {
                            break;
                        }
                    }
                    content.append("\r\n").append(line);
                    contentSize += line.getBytes().length;
                    if (contentSize > maxSize) {
                        return R.failed("查询结果过大，请输入更长的关键字");
                    }
                }
            }


            return getContentResult(content);
        } catch (Exception e) {
            log.error("文件读取异常", e);
            return R.failed("系统异常：" + e.getMessage());
        }
    }

    /**
     * 匹配行内容
     */
    private boolean matchLine(String line, String keyword, Pattern pattern) {
        return pattern != null ? pattern.matcher(line).find() : line.contains(keyword);
    }


    /**
     * 获取内容结果
     */
    private R<String> getContentResult(StringBuilder content) {
        String contentString = content.toString();
        // 去除结尾的 \r\n\r\n
        if (content.length() > 0 && contentString.endsWith("\r\n\r\n")) {
            contentString = contentString.substring(0, content.length() - 4);
        }
        return content.length() > 0 ? R.success(contentString) : R.failed("未找到匹配内容");
    }

    /**
     * 查询文件系统目录结构
     */
    private R<List<Map<String, String>>> queryFileSystem(String path, String query) {
        try {
            File dir = new File(path);
            if (!dir.exists() || !dir.isDirectory()) {
                return R.failed("目录不存在");
            }

            // 获取目录文件列表
            File[] files = dir.listFiles();
            if (files == null) {
                return R.failed("目录不可读");
            }

            // 初始化白名单缓存
            initializeWhitelistCache();

            // 查找默认路径
            JSONObject defaultPath = whitelistListCache.stream()
                    .filter(item -> item.getStr("resourcePath").equals(resourcePathDefault))
                    .findFirst()
                    .orElse(new JSONObject());

            return R.success(Arrays.stream(files)
                    .sorted((a, b) -> Long.compare(b.lastModified(), a.lastModified()))
                    .filter(f -> StrUtil.isBlank(query) || f.getName().contains(query))
                    // 生产环境仅显示生产日志
                    .filter(f -> "dev".equals(active) || (prodBlacklistListCache.stream()
                            .map(item -> !f.getName().contains(item))
                            .filter(b -> !b)
                            .findFirst().orElse(true)))
                    .limit(maxLogFiles)
                    .map(f -> {
                        Map<String, String> info = new HashMap<>();
                        info.put("fileName", f.getName());
                        info.put("filePath", path + (path.contains("/") ? "/" : "\\") + f.getName());
                        info.put("fileSize", String.valueOf(f.length()));
                        return info;
                    }).collect(Collectors.toList())).add("resourcePathDefault", defaultPath);
        } catch (SecurityException e) {
            return R.failed("目录访问权限不足");
        }
    }

    /**
     * 初始化白名单缓存
     */
    private void initializeWhitelistCache() {

        if (whitelistListCache.isEmpty()) {
            String utf8Str = new String(resourcePathWhitelistStr.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            if (StrUtil.isBlank(utf8Str)) {
                utf8Str = "[]";
            }
            whitelistListCache = JSONUtil.toList(utf8Str, JSONObject.class);
        }
        if (prodBlacklistListCache.isEmpty()) {
            String utf8Str = new String(prodBlacklistList.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            if (StrUtil.isBlank(utf8Str)) {
                utf8Str = "[]";
            }
            prodBlacklistListCache = JSONUtil.toList(utf8Str, String.class);
        }
    }

    /**
     * 更新日志缓存并执行清理
     */
    private void updateLogCache(String path, String key, R<String> result) {
        LOG_CACHE.computeIfAbsent(path, k -> new HashMap<>()).put(key, result);

        totalCacheBytes += Optional.ofNullable(result.getData()).map(String::length).orElse(0);

        long totalCacheBytesMb = totalCacheBytes / 1024 / 1024;
        if (LOG_CACHE.size() > maxCacheEntries || totalCacheBytesMb > maxCacheSize) {
            log.info("清理日志缓存 当前条目[{}] 大小[{}MB]", LOG_CACHE.size(), totalCacheBytesMb);
            LOG_CACHE.clear();
            totalCacheBytes = 0L;
        }
    }

    /**
     * 检查并清理路径缓存
     */
    private void checkAndClearPathCache() {
        if (pathRequestCount > maxPathRequests) {
            log.info("清理路径缓存 当前条目[{}] 请求次数[{}]", PATH_CACHE.size(), pathRequestCount);
            PATH_CACHE.clear();
            pathRequestCount = 0L;
        }
    }

    /**
     * 记录缓存状态
     */
    private void logCacheStatus(String path, String key) {
        log.info("日志缓存命中 [{}] 关键词[{}] 缓存量[{}] 大小[{}MB]",
                path, key, LOG_CACHE.size(), totalCacheBytes / 1024 / 1024);
    }
    /* ---------------------------------------- endregion ---------------------------------------- */
}
